package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.respository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author CHERRY
 * @date 2018-09-09-1:39
 */
@Service
public class SearchService {
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private SpecificationClient specificationClient;
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 搜索
     *
     * @param searchRequest
     * @return
     */
    public SearchResult search(SearchRequest searchRequest) {
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            return null;
        }
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //排序、分页
        searchWithSortAndPage(searchRequest, queryBuilder);

        //key全文搜索
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("all", key).operator(Operator.AND));

        this.buildQueryWithFilter(searchRequest, boolQueryBuilder);

        queryBuilder.withQuery(boolQueryBuilder);


        //资源过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));

        //分类聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("cid3"));
        //品牌聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));
        //解析聚合结果
        AggregatedPage<Goods> pageGoods = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());
        //商品分类集合
        List<Brand> brands = this.getBrandsAggResult(pageGoods);
        //商品品牌集合
        List<Category> categories = this.getCategoriesAggResult(pageGoods);

        //初始化 规格参数数据集合
        List<Map<String, Object>> specsMap = null;
        //只有一个分类,聚合规格参数
        if (categories.size() == 1) {

            specsMap = this.getSpecs(categories, boolQueryBuilder);

        }

        //搜索结果数量
        long total = pageGoods.getTotalElements();

        //总页数
        Long totalPage = (total + searchRequest.getSize() - 1) / searchRequest.getSize();

        //Aggregation getTotalPages int类型
        return new SearchResult(pageGoods.getTotalElements(), totalPage, pageGoods.getContent(), categories, brands, specsMap);

    }

    /**
     * 构建过滤查询
     *
     * @param searchRequest
     * @param queryBuilder
     */
    private void buildQueryWithFilter(SearchRequest searchRequest, BoolQueryBuilder queryBuilder) {
        //过滤参数
        Map<String, String> filterMap = searchRequest.getFilter();

        if (!CollectionUtils.isEmpty(filterMap)) {
            //过滤查询
            BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
            for (Map.Entry<String, String> entry : filterMap.entrySet()) {
                //过滤参数名
                String filterSpecName = entry.getKey();

                //更改查询词条名
                if (filterSpecName == "分类") {

                    filterSpecName = "cid3";

                } else if (filterSpecName == "品牌") {

                    filterSpecName = "brandId";
                } else {

                    filterSpecName = "specs." + filterSpecName + ".keyword";
                }

                //过滤参数值
                String filterSpecValue = entry.getValue();
                //
                filterQueryBuilder.must(QueryBuilders.termQuery(filterSpecName, filterSpecValue));
                //添加过滤条件
                queryBuilder.filter(filterQueryBuilder);
            }

        }
    }

    /**
     * 查询分类的规格参数
     *
     * @param categories
     * @param basicQuery
     * @return
     */
    private List<Map<String, Object>> getSpecs(List<Category> categories, BoolQueryBuilder basicQuery) {
        List<Map<String, Object>> specsMap = new ArrayList<>();
        //查询所有可搜索的规格参数
        List<SpecParam> specParams = this.specificationClient.querySpecParamByCid(categories.get(0).getId(), null, true, null);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //全文搜索
        nativeSearchQueryBuilder.withQuery(basicQuery);
        //遍历 根据参数名聚合
        specParams.forEach(specParam -> {
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(specParam.getName() + "Agg").field("specs." + specParam.getName() + ".keyword"));
        });
        //查询桶
        Map<String, Aggregation> aggregationMap = this.elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), SearchResponse::getAggregations).asMap();

        //遍历参数集合
        specParams.forEach(specParam -> {
            //通过参数名,获取参数所在的桶
            StringTerms aggregation = (StringTerms) aggregationMap.get(specParam.getName() + "Agg");
            List<StringTerms.Bucket> buckets = aggregation.getBuckets();
            Map<String, Object> map = new HashMap<>(16);
            map.put("k", specParam.getName());
            map.put("options", buckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()));
            specsMap.add(map);
        });
        return specsMap;
    }

    /**
     * 解析分类聚合结果
     *
     * @param pageGoods
     * @return
     */
    private List<Category> getCategoriesAggResult(AggregatedPage<Goods> pageGoods) {
        LongTerms categoryAgg = (LongTerms) pageGoods.getAggregation("categoryAgg");
        //获取category桶
        List<LongTerms.Bucket> categoryAggBuckets = categoryAgg.getBuckets();
        //获取分类桶的id集合
        List<Long> cids = categoryAggBuckets.stream().map(categoryAggBucket ->
                categoryAggBucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        //初始化category 集合
        List<Category> categories = new ArrayList<>();
        List<String> names = this.categoryClient.queryCategoryNamesByIds(cids);
        for (int i = 0; i < names.size(); i++) {
            Category category = new Category();
            //list集合有序 id与name 对应
            category.setName(names.get(i));
            category.setId(cids.get(i));
            categories.add(category);
        }
        return categories;
    }

    /**
     * 解析品牌聚合结果
     *
     * @param pageGoods
     * @return
     */
    private List<Brand> getBrandsAggResult(AggregatedPage<Goods> pageGoods) {
        LongTerms brandAgg = (LongTerms) pageGoods.getAggregation("brandAgg");
        //获取brand桶
        List<LongTerms.Bucket> brandAggBuckets = brandAgg.getBuckets();

        //获取品牌桶的id集合
        List<Long> bids = brandAggBuckets.stream().map(brandAggBucket ->
                brandAggBucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        // 
        if (CollectionUtils.isEmpty(bids)) {
            return new ArrayList<>();
        }
        return this.brandClient.queryBrandByIds(bids);
    }

    /**
     * 构建查询基本条件,排序分页
     *
     * @param searchRequest
     * @param queryBuilder
     */
    private void searchWithSortAndPage(SearchRequest searchRequest, NativeSearchQueryBuilder queryBuilder) {
        //排序字段
        String sortBy = searchRequest.getSortBy();
        if (StringUtils.isNotBlank(sortBy)) {
            //是否降序
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(
                    searchRequest.getDescending() ? SortOrder.DESC : SortOrder.ASC));
        }
        //分页
        queryBuilder.withPageable(PageRequest.of((searchRequest.getPage() - 1), searchRequest.getSize()));
    }

    /**
     * 加载数据，保存到索引库
     */
    public void loadData() {
        //建立索引 映射
        elasticsearchTemplate.createIndex(Goods.class);
        elasticsearchTemplate.putMapping(Goods.class);
        //分页查询 spu
        String key = null;
        Boolean saleable = true;
        Integer page = 1;
        Integer rows = 100;
        //总页数
        int size = 0;
        do {
            //初始化
            List<Goods> goodsList = new ArrayList<>();
            PageResult<SpuBo> spuBoPageResult = goodsClient.querySpuByPage(key, saleable, page, rows);
            List<SpuBo> spuBos = spuBoPageResult.getItems();
            size = spuBos.size();
           /*
             //空指针
             if (size == 0){
                break;
            }*/
            try {
                spuBos.forEach(spuBo -> {
                    try {
                        goodsList.add(buildGoods(spuBo));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                //空指针？
                break;
            }
            this.goodsRepository.saveAll(goodsList);
            //查询下一页
            page++;
        } while (size == 100);
    }


    /**
     * 构建保存到索引库的商品
     *
     * @param spuBo
     * @return
     * @throws IOException
     */
    private Goods buildGoods(SpuBo spuBo) throws IOException {
        Goods goods = new Goods();
        //查询分类
        List<String> names = this.categoryClient.queryCategoryNamesByIds(
                Arrays.asList(spuBo.getCid1(), spuBo.getCid2(), spuBo.getCid3()));
        //查看品牌
        Brand brand = this.brandClient.queryBrandByBid(spuBo.getBrandId());
        //查看spudetail
        SpuDetail spuDetail = this.goodsClient.querySpuDetail(spuBo.getId());
        //查看sku
        List<Sku> skus = this.goodsClient.querySKu(spuBo.getId());
        //初始化价格集合
        List<Long> price = new ArrayList<>();
        //sku key-value 数据
        List<Map<String, Object>> mapList = new ArrayList<>();
        skus.forEach(sku -> {
            price.add(sku.getPrice());
            Map<String, Object> map = new HashMap<>(16);
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" : sku.getImages().split(",")[0]);
            map.put("price", sku.getPrice());
            mapList.add(map);
        });

        //获取通用规格参数值
        Map<String, Object> genericParam = MAPPER.readValue(spuDetail.getGenericSpec(),
                new TypeReference<Map<String, Object>>() {
                });
        //获取特殊规格参数
        Map<String, Object> specialParam = MAPPER.readValue(spuDetail.getSpecialSpec(),
                new TypeReference<Map<String, Object>>() {
                });
        //查询规格参数
        List<SpecParam> specParams = this.specificationClient.querySpecParamByCid(spuBo.getCid3(), null, true, null);
        //查询spudetail 通用参数
        //Map<String, Object> genericSpecMap =
        Map<String, Object> specs = new HashMap<>();
        specParams.forEach(specParam -> {
            //通用参数
            if (specParam.getGeneric()) {
                //参数值
                String value = genericParam.get(specParam.getId().toString()).toString();
                if (specParam.getNumeric()) {
                    //数值区间特殊处理
                    value = this.chooseSegments(value, specParam);
                }
                specs.put(specParam.getName(), value);
            } else {
                //特殊参数
                List<Object> value = (List<Object>) specialParam.get(specParam.getId().toString());
                specs.put(specParam.getName(), value);
            }
        });
        goods.setId(spuBo.getId());
        goods.setBrandId(spuBo.getBrandId());
        goods.setCid1(spuBo.getCid1());
        goods.setCid2(spuBo.getCid2());
        goods.setCid3(spuBo.getCid3());
        goods.setCreateTime(spuBo.getCreateTime());
        goods.setPrice(price);
        goods.setSubTitle(spuBo.getSubTitle());
        //标题 分类 品牌
        goods.setAll(spuBo.getTitle() + " " + StringUtils.join(names, "") + " " + brand.getName());
        goods.setSkus(MAPPER.writeValueAsString(mapList));
        goods.setSpecs(specs);
        return goods;
    }

    /**
     * 参数区间
     *
     * @param value
     * @param specParam
     * @return
     */
    private String chooseSegments(String value, SpecParam specParam) {
        Double val = Double.valueOf(value);

        //初始化 参数结果
        String result = "其它";
        String segments = specParam.getSegments();
        for (String segment : segments.split(",")) {
            String[] segs = segment.split("-");
            //获取取值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + specParam.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + specParam.getUnit() + "以下";
                } else {
                    result = segment + specParam.getUnit();
                }
                break;
            }
        }
        return result;
    }
}
